﻿///////////////////////////////////////////
// author     : chen yong
// create time: 2017/06/21
// modify time: 
// description: Bloom post-processed on game effects based on depth
///////////////////////////////////////////

using UnityEngine;
using System.Collections.Generic;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class CameraEffectBloom : MonoBehaviour
{
    public enum LensFlareStyle
    {
        Ghosting = 0,
        Anamorphic = 1,
        Combined = 2,
    }

    public enum TweakMode
    {
        Basic = 0,
        Complex = 1,
    }

    public enum HDRBloomMode
    {
        Auto = 0,
        On = 1,
        Off = 2,
    }

    public enum BloomScreenBlendMode
    {
        Screen = 0,
        Add = 1,
    }

    public enum BloomQuality
    {
        Cheap = 0,
        High = 1,
    }

    public BloomQuality quality = BloomQuality.High;
    public TweakMode tweakMode = 0;
    public BloomScreenBlendMode screenBlendMode = BloomScreenBlendMode.Add;

    public HDRBloomMode hdr = HDRBloomMode.Auto;
    private bool doHdr = false;   

    public float bloomIntensity = 0.5f;
    public float bloomThreshold = 0.5f;
    private Color bloomThresholdColor = Color.white;
    public int bloomBlurIterations = 2;
    public float sepBlurSpread = 2.5f;

    private int hollywoodFlareBlurIterations = 2;
    private float flareRotation = 0.0f;
    private LensFlareStyle lensflareMode = (LensFlareStyle)1;
    private float hollyStretchWidth = 2.5f;
    private float lensflareIntensity = 0.0f;
    private float lensflareThreshold = 0.3f;
    private float lensFlareSaturation = 0.75f;
    private Color flareColorA = new Color(0.4f, 0.4f, 0.8f, 0.75f);
    private Color flareColorB = new Color(0.4f, 0.8f, 0.8f, 0.75f);
    private Color flareColorC = new Color(0.8f, 0.4f, 0.8f, 0.75f);
    private Color flareColorD = new Color(0.8f, 0.4f, 0.0f, 0.75f);
    private Texture2D lensFlareVignetteMask;

    private Shader lensFlareShader;
    private Material lensFlareMaterial;

    public Shader screenBlendShader;
    private Material screenBlend;

    public Shader blurAndFlaresShader;
    private Material blurAndFlaresMaterial;

    public Shader brightPassFilterShader;
    private Material brightPassFilterMaterial;

    private Camera glowCamera;
    private GameObject glowCameraObject;
    private RenderTexture glowTexture;

    private Camera depthCamera;
    private GameObject depthCameraObject;
    private RenderTexture depthTexture;

    private GameObject DepthCameraObject
    {
        get
        {
            if (!depthCameraObject)
            {
                depthCameraObject = new GameObject("depthCameraObject");
                depthCameraObject.hideFlags = HideFlags.HideAndDontSave;
                depthCameraObject.AddComponent<Camera>();
                DepthCamera.hideFlags = HideFlags.HideAndDontSave;
            }
            return depthCameraObject;
        }
    }

    private Camera DepthCamera
    {
        get
        {
            if (depthCamera == null)
            {
                depthCamera = DepthCameraObject.GetComponent<Camera>();
            }
            return depthCamera;
        }
    }

    private GameObject GlowCameraObject
    {
        get
        {
            if (!glowCameraObject)
            {
                glowCameraObject = new GameObject("glowCameraObject");
                glowCameraObject.hideFlags = HideFlags.HideAndDontSave;
                glowCameraObject.AddComponent<Camera>();
                GlowCamera.hideFlags = HideFlags.HideAndDontSave;
            }
            return glowCameraObject;
        }
    }
    private Camera GlowCamera
    {
        get
        {
            if (glowCamera == null)
            {
                glowCamera = GlowCameraObject.GetComponent<Camera>();
            }
            return glowCamera;
        }
    }

    void OnEnable()
    {
        if (!screenBlendShader)
            screenBlendShader = Shader.Find("Hidden/BlendForBloom");

        if (!screenBlend)
        {
            screenBlend = new Material(screenBlendShader);
            screenBlend.hideFlags = HideFlags.HideAndDontSave;
        }

        if (!blurAndFlaresShader)
            blurAndFlaresShader = Shader.Find("Hidden/BlurAndFlares");

        if (!blurAndFlaresMaterial)
        {
            blurAndFlaresMaterial = new Material(blurAndFlaresShader);
            blurAndFlaresMaterial.hideFlags = HideFlags.HideAndDontSave;
        }

        if (!brightPassFilterShader)
            brightPassFilterShader = Shader.Find("Hidden/BrightPassFilter2");

        if (!brightPassFilterMaterial)
        {
            brightPassFilterMaterial = new Material(brightPassFilterShader);
            brightPassFilterMaterial.hideFlags = HideFlags.HideAndDontSave;
        }
    }

    void OnDisable()
    {
        if (screenBlend)
            DestroyImmediate(screenBlend);

        if (blurAndFlaresMaterial)
            DestroyImmediate(blurAndFlaresMaterial);

        if (brightPassFilterMaterial)
            DestroyImmediate(brightPassFilterMaterial);

        if (depthCamera)
            DestroyImmediate(DepthCamera);

        if (depthCameraObject)
            DestroyImmediate(DepthCameraObject);

        if (glowCamera)
            DestroyImmediate(GlowCamera);

        if (glowCameraObject)
            DestroyImmediate(GlowCameraObject);
    }

    private void SetupDepthCamera()
    {
        DepthCamera.CopyFrom(GetComponent<Camera>());
        DepthCamera.allowMSAA = false;

        DepthCamera.targetTexture = depthTexture;

        DepthCamera.enabled = false;
    }

    private void SetupGlowCamera()
    {
        GlowCamera.CopyFrom(GetComponent<Camera>());
        GlowCamera.clearFlags = CameraClearFlags.Nothing;
        GlowCamera.rect = new Rect(0, 0, 1, 1);
        GlowCamera.backgroundColor = new Color(0, 0, 0, 0);
        GlowCamera.cullingMask = 1 << LayerMask.NameToLayer("Effect");
        GlowCamera.orthographic = false;
        GlowCamera.targetTexture = glowTexture;

        GlowCamera.enabled = false;
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (!gameObject.activeSelf || !enabled)
            return;

        //Graphics.Blit(glowTexture, destination);


        // screen blend is not supported when HDR is enabled (will cap values)

        doHdr = false;
        if (hdr == HDRBloomMode.Auto)
            doHdr = source.format == RenderTextureFormat.ARGBHalf && GetComponent<Camera>().allowHDR;
        else
        {
            doHdr = hdr == HDRBloomMode.On;
        }

        BloomScreenBlendMode realBlendMode = screenBlendMode;
        if (doHdr)
            realBlendMode = BloomScreenBlendMode.Add;

        var rtFormat = (doHdr) ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.Default;
        var rtW2 = source.width / 2;
        var rtH2 = source.height / 2;
        var rtW4 = source.width / 4;
        var rtH4 = source.height / 4;

        float widthOverHeight = (1.0f * source.width) / (1.0f * source.height);
        float oneOverBaseSize = 1.0f / 512.0f;

        depthTexture = RenderTexture.GetTemporary(source.width, source.height, 24, RenderTextureFormat.Depth);
        glowTexture = RenderTexture.GetTemporary(source.width, source.height, 0, rtFormat);
        SetupDepthCamera();
        SetupGlowCamera();

        // render depth texture first
        DepthCamera.Render();

        var active = RenderTexture.active;
        RenderTexture.active = glowTexture;
        GL.Clear(true, true, Color.clear);
        RenderTexture.active = active;
        GlowCamera.SetTargetBuffers(glowTexture.colorBuffer, depthTexture.depthBuffer);
        GlowCamera.Render();

        RenderTexture.ReleaseTemporary(depthTexture);

        // downsample
        RenderTexture quarterRezColor = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);
        RenderTexture halfRezColorDown = RenderTexture.GetTemporary(rtW2, rtH2, 0, rtFormat);
        if (quality > BloomQuality.Cheap)
        {
            Graphics.Blit(glowTexture, halfRezColorDown, screenBlend, 2);
            RenderTexture rtDown4 = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);
            Graphics.Blit(halfRezColorDown, rtDown4, screenBlend, 2);
            Graphics.Blit(rtDown4, quarterRezColor, screenBlend, 6);
            RenderTexture.ReleaseTemporary(rtDown4);
        }
        else
        {
            Graphics.Blit(glowTexture, halfRezColorDown);
            Graphics.Blit(halfRezColorDown, quarterRezColor, screenBlend, 6);
        }
        RenderTexture.ReleaseTemporary(halfRezColorDown);

        // cut colors (thresholding)
        RenderTexture secondQuarterRezColor = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);
        BrightFilter(bloomThreshold * bloomThresholdColor, quarterRezColor, secondQuarterRezColor);

        // blurring

        if (bloomBlurIterations < 1) bloomBlurIterations = 1;
        else if (bloomBlurIterations > 10) bloomBlurIterations = 10;

        for (int iter = 0; iter < bloomBlurIterations; iter++)
        {
            float spreadForPass = (1.0f + (iter * 0.25f)) * sepBlurSpread;

            // vertical blur
            RenderTexture blur4 = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);
            blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(0.0f, spreadForPass * oneOverBaseSize, 0.0f, 0.0f));
            Graphics.Blit(secondQuarterRezColor, blur4, blurAndFlaresMaterial, 4);
            RenderTexture.ReleaseTemporary(secondQuarterRezColor);
            secondQuarterRezColor = blur4;

            // horizontal blur
            blur4 = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);
            blurAndFlaresMaterial.SetVector("_Offsets", new Vector4((spreadForPass / widthOverHeight) * oneOverBaseSize, 0.0f, 0.0f, 0.0f));
            Graphics.Blit(secondQuarterRezColor, blur4, blurAndFlaresMaterial, 4);
            RenderTexture.ReleaseTemporary(secondQuarterRezColor);
            secondQuarterRezColor = blur4;

            if (quality > BloomQuality.Cheap)
            {
                if (iter == 0)
                {
                    Graphics.SetRenderTarget(quarterRezColor);
                    GL.Clear(false, true, Color.black); // Clear to avoid RT restore
                    Graphics.Blit(secondQuarterRezColor, quarterRezColor);
                }
                else
                {
                    quarterRezColor.MarkRestoreExpected(); // using max blending, RT restore expected
                    Graphics.Blit(secondQuarterRezColor, quarterRezColor, screenBlend, 10);
                }
            }
        }

        if (quality > BloomQuality.Cheap)
        {
            Graphics.SetRenderTarget(secondQuarterRezColor);
            GL.Clear(false, true, Color.black); // Clear to avoid RT restore
            Graphics.Blit(quarterRezColor, secondQuarterRezColor, screenBlend, 6);
        }

        // lens flares: ghosting, anamorphic or both (ghosted anamorphic flares)

        if (lensflareIntensity > Mathf.Epsilon)
        {

            RenderTexture rtFlares4 = RenderTexture.GetTemporary(rtW4, rtH4, 0, rtFormat);

            if (lensflareMode == 0)
            {
                // ghosting only

                BrightFilter(lensflareThreshold, secondQuarterRezColor, rtFlares4);

                if (quality > BloomQuality.Cheap)
                {
                    // smooth a little
                    blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(0.0f, (1.5f) / (1.0f * quarterRezColor.height), 0.0f, 0.0f));
                    Graphics.SetRenderTarget(quarterRezColor);
                    GL.Clear(false, true, Color.black); // Clear to avoid RT restore
                    Graphics.Blit(rtFlares4, quarterRezColor, blurAndFlaresMaterial, 4);

                    blurAndFlaresMaterial.SetVector("_Offsets", new Vector4((1.5f) / (1.0f * quarterRezColor.width), 0.0f, 0.0f, 0.0f));
                    Graphics.SetRenderTarget(rtFlares4);
                    GL.Clear(false, true, Color.black); // Clear to avoid RT restore
                    Graphics.Blit(quarterRezColor, rtFlares4, blurAndFlaresMaterial, 4);
                }

                // no ugly edges!
                Vignette(0.975f, rtFlares4, rtFlares4);
                BlendFlares(rtFlares4, secondQuarterRezColor);
            }
            else
            {

                //Vignette (0.975ff, rtFlares4, rtFlares4);
                //DrawBorder(rtFlares4, screenBlend, 8);

                float flareXRot = 1.0f * Mathf.Cos(flareRotation);
                float flareyRot = 1.0f * Mathf.Sin(flareRotation);

                float stretchWidth = (hollyStretchWidth * 1.0f / widthOverHeight) * oneOverBaseSize;

                blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(flareXRot, flareyRot, 0.0f, 0.0f));
                blurAndFlaresMaterial.SetVector("_Threshhold", new Vector4(lensflareThreshold, 1.0f, 0.0f, 0.0f));
                blurAndFlaresMaterial.SetVector("_TintColor", new Vector4(flareColorA.r, flareColorA.g, flareColorA.b, flareColorA.a) * flareColorA.a * lensflareIntensity);
                blurAndFlaresMaterial.SetFloat("_Saturation", lensFlareSaturation);

                // "pre and cut"
                quarterRezColor.DiscardContents();
                Graphics.Blit(rtFlares4, quarterRezColor, blurAndFlaresMaterial, 2);
                // "post"
                rtFlares4.DiscardContents();
                Graphics.Blit(quarterRezColor, rtFlares4, blurAndFlaresMaterial, 3);

                blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(flareXRot * stretchWidth, flareyRot * stretchWidth, 0.0f, 0.0f));
                // stretch 1st
                blurAndFlaresMaterial.SetFloat("_StretchWidth", hollyStretchWidth);
                quarterRezColor.DiscardContents();
                Graphics.Blit(rtFlares4, quarterRezColor, blurAndFlaresMaterial, 1);
                // stretch 2nd
                blurAndFlaresMaterial.SetFloat("_StretchWidth", hollyStretchWidth * 2.0f);
                rtFlares4.DiscardContents();
                Graphics.Blit(quarterRezColor, rtFlares4, blurAndFlaresMaterial, 1);
                // stretch 3rd
                blurAndFlaresMaterial.SetFloat("_StretchWidth", hollyStretchWidth * 4.0f);
                quarterRezColor.DiscardContents();
                Graphics.Blit(rtFlares4, quarterRezColor, blurAndFlaresMaterial, 1);

                // additional blur passes
                for (int iter = 0; iter < hollywoodFlareBlurIterations; iter++)
                {
                    stretchWidth = (hollyStretchWidth * 2.0f / widthOverHeight) * oneOverBaseSize;

                    blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(stretchWidth * flareXRot, stretchWidth * flareyRot, 0.0f, 0.0f));
                    rtFlares4.DiscardContents();
                    Graphics.Blit(quarterRezColor, rtFlares4, blurAndFlaresMaterial, 4);

                    blurAndFlaresMaterial.SetVector("_Offsets", new Vector4(stretchWidth * flareXRot, stretchWidth * flareyRot, 0.0f, 0.0f));
                    quarterRezColor.DiscardContents();
                    Graphics.Blit(rtFlares4, quarterRezColor, blurAndFlaresMaterial, 4);
                }

                if (lensflareMode == (LensFlareStyle)1)
                    // anamorphic lens flares
                    AddTo(1.0f, quarterRezColor, secondQuarterRezColor);
                else
                {
                    // "combined" lens flares

                    Vignette(1.0f, quarterRezColor, rtFlares4);
                    BlendFlares(rtFlares4, quarterRezColor);
                    AddTo(1.0f, quarterRezColor, secondQuarterRezColor);
                }
            }
            RenderTexture.ReleaseTemporary(rtFlares4);
        }

        int blendPass = (int)realBlendMode;
        //if (Mathf.Abs(chromaticBloom) < Mathf.Epsilon)
        //	blendPass += 4;

        screenBlend.SetFloat("_Intensity", bloomIntensity);
        screenBlend.SetTexture("_ColorBuffer", source);

        if (quality > BloomQuality.Cheap)
        {
            RenderTexture halfRezColorUp = RenderTexture.GetTemporary(rtW2, rtH2, 0, rtFormat);
            Graphics.Blit(secondQuarterRezColor, halfRezColorUp);
            Graphics.Blit(halfRezColorUp, destination, screenBlend, blendPass);
            RenderTexture.ReleaseTemporary(halfRezColorUp);
        }
        else
            Graphics.Blit(secondQuarterRezColor, destination, screenBlend, blendPass);

        RenderTexture.ReleaseTemporary(quarterRezColor);
        RenderTexture.ReleaseTemporary(secondQuarterRezColor);

        RenderTexture.ReleaseTemporary(glowTexture);
    }

    private void AddTo(float intensity_, RenderTexture from, RenderTexture to)
    {
        screenBlend.SetFloat("_Intensity", intensity_);
        to.MarkRestoreExpected(); // additive blending, RT restore expected
        Graphics.Blit(from, to, screenBlend, 9);
    }

    private void BlendFlares(RenderTexture from, RenderTexture to)
    {
        lensFlareMaterial.SetVector("colorA", new Vector4(flareColorA.r, flareColorA.g, flareColorA.b, flareColorA.a) * lensflareIntensity);
        lensFlareMaterial.SetVector("colorB", new Vector4(flareColorB.r, flareColorB.g, flareColorB.b, flareColorB.a) * lensflareIntensity);
        lensFlareMaterial.SetVector("colorC", new Vector4(flareColorC.r, flareColorC.g, flareColorC.b, flareColorC.a) * lensflareIntensity);
        lensFlareMaterial.SetVector("colorD", new Vector4(flareColorD.r, flareColorD.g, flareColorD.b, flareColorD.a) * lensflareIntensity);
        to.MarkRestoreExpected(); // additive blending, RT restore expected
        Graphics.Blit(from, to, lensFlareMaterial);
    }

    private void BrightFilter(float thresh, RenderTexture from, RenderTexture to)
    {
        brightPassFilterMaterial.SetVector("_Threshhold", new Vector4(thresh, thresh, thresh, thresh));
        Graphics.Blit(from, to, brightPassFilterMaterial, 0);
    }

    private void BrightFilter(Color threshColor, RenderTexture from, RenderTexture to)
    {
        brightPassFilterMaterial.SetVector("_Threshhold", threshColor);
        Graphics.Blit(from, to, brightPassFilterMaterial, 1);
    }

    private void Vignette(float amount, RenderTexture from, RenderTexture to)
    {
        if (lensFlareVignetteMask)
        {
            screenBlend.SetTexture("_ColorBuffer", lensFlareVignetteMask);
            to.MarkRestoreExpected(); // using blending, RT restore expected
            Graphics.Blit(from == to ? null : from, to, screenBlend, from == to ? 7 : 3);
        }
        else if (from != to)
        {
            Graphics.SetRenderTarget(to);
            GL.Clear(false, true, Color.black); // clear destination to avoid RT restore
            Graphics.Blit(from, to);
        }
    }
}